#pragma once

#include <atomic>
#include <cstddef>
#include <type_traits>
#include <memory>

#include "utils/logger.h"

namespace utils {

    /**
     * @brief Single-producer single-consumer lock-free queue.
     *
     * An SPSC queue based on ring buffer, supporting move semantics and batch operations.
     * Cache-line alignment is used to avoid false sharing, optimizing performance on multi-core systems.
     */
    template<typename T, size_t Capacity>
    class SPSCQueue {
        static_assert(Capacity > 0, "Capacity must be positive");
        static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two");

     public:
        SPSCQueue();
        ~SPSCQueue() = default;

        /// Delete copy constructor and assignment.
        SPSCQueue(const SPSCQueue&) = delete;
        SPSCQueue& operator=(const SPSCQueue&) = delete;

        /**
         * @brief Attempt to enqueue an element using move semantics
         * @param item The element to be pushed
         * @return Return true upon successful insertion, false when the queue is full
         */
        bool try_push(T&& item);

        /**
         * @brief Attempt to dequeue an element using move semantics
         * @param item Receive the popped element
         * @return Return true upon successful extraction, false when the queue is empty
         */
        bool try_pop(T& item);

        /**
         * @brief Push multiple elements in a batch
         * @param items Array of elements
         * @param count Number of elements
         * @return The actual number of elements pushed
         */
        size_t push_bulk(T* items, size_t count);

        /**
         * @brief Pop multiple elements in a batch
         * @param items Array to receive elements
         * @param count Number of requested elements
         * @return The actual number of elements popped
         */
        size_t pop_bulk(T* items, size_t count);

        /**
         * @brief Check if the queue is empty
         * @note Thread-safe, but the state may change immediately afterward
         */
        bool empty() const noexcept;

        /**
         * @brief Check if the queue is full
         * @note Thread-safe, but the state may change immediately afterward
         */
        bool full() const noexcept;

        /**
         * @brief Get current queue size
         */
        size_t size() const noexcept;

        /**
         * @brief Get queue capacity
         */
        constexpr size_t capacity() const noexcept { return Capacity; }

        /**
         * @brief Clear the queue
         * @warning Only safe if both producer and consumer threads have stopped
         */
        void clear();

        /**
         * @brief Get queue statistics
         */
        struct Stats {
            size_t push_count;
            size_t pop_count;
            size_t push_failures;
            size_t pop_failures;
        };

        Stats get_stats() const;

     private:
        /// Cache-line size (usually 64B)
        static constexpr size_t CACHE_LINE_SIZE = 64;

        /// Ring buffer index calculation
        size_t next_index(size_t index) const noexcept {
            return (index + 1) & (Capacity - 1);        /// Leverage the power-of-two property for fast modulo operation
        }

        /// Use cache-line alignment to avoid false sharing
        alignas(CACHE_LINE_SIZE) std::atomic<size_t> m_head;   /// Consumer index
        alignas(CACHE_LINE_SIZE) std::atomic<size_t> m_tail;   /// Producer index

        /// Statistic data
        mutable std::atomic<size_t> m_push_count{0};
        mutable std::atomic<size_t> m_pop_count{0};
        mutable std::atomic<size_t> m_push_failures{0};
        mutable std::atomic<size_t> m_pop_failures{0};

        /// Ring buffer
        alignas(CACHE_LINE_SIZE) std::aligned_storage_t<sizeof(T), alignof(T)> m_buffer[Capacity];

        /// Ensure that T is movable
        static_assert(std::is_nothrow_move_constructible_v<T>, "T must be nothrow move constructible");
        static_assert(std::is_nothrow_move_assignable_v<T>, "T must be nothrow move assignable");
    };
    } // namespace utils

#include "utils/spsc_queue.ipp"